/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    file_streamer.cpp  
  Description: 

*/

#include "stdafx.h"


using namespace m::alloc;
using namespace m::error;

#define TAG_FILESTREAMER L"file_streamer: "

#define CABINET_EXT L".pkg"
#define BUFSZ 1 << 15

namespace m
{
	namespace io
	{
		file_streamer::file_streamer()
		{
		}

		void file_streamer::read_file(file_buffer *Fs, LPCWSTR FileName)
		{
			assert(Fs);
			if (Fs)
			{
				Fs->clear();
				Fs->_Operation = FileOperation::ReadFile;
				assign(Fs->_FileName, FileName);
				this->send(L"us.sl.m.readStart", Fs);
			}
		}

		void file_streamer::write_file(file_buffer *Fs, LPCWSTR FileName)
		{
			assert(Fs);
			if (Fs)
			{
				Fs->_Operation = FileOperation::WriteFile;
				assign(Fs->_FileName, FileName);
				this->send(L"us.sl.m.writeStart", Fs);
			}
		}

		void file_streamer::process_entry()
		{
			DBGL(TAG_FILESTREAMER L"Starting file steamer.");

			while (started())
			{
				while (next_message());
			}
		}

		bool file_streamer::handle_message(message* m)
		{
			ref_storage track1;
			process* sender = lock<process>(track1, m->sender());

			if (sender == this)
			{
				if (m->type_equals(L"us.sl.m.readStart") || m->type_equals(L"us.sl.m.writeStart"))
				{
					if (m->data())
					{
						file_buffer* fs = dynamic_cast<file_buffer*>(m->data());
						if (fs)
						{
							HandleFile(fs);
							if (fs->_Operation == FileOperation::ReadFile)
							{
								this->send(L"us.sl.m.readComplete", fs);
							}
							else if (fs->_Operation == FileOperation::WriteFile)
							{
								this->send(L"us.sl.m.writeComplete", fs);
							}
						}
					}
					return true;
				}
			}
			return false;
		}

		void file_streamer::HandleFile(file_buffer* Fs)
		{
			Fs->_Hr = S_OK;

			WCHAR FilePath[MAX_PATH] = { 0 };
			WCHAR ArchivePath[MAX_PATH] = { 0 };

			// Try to parse out the archive path of the file, if any.
			WCHAR* Cur = &Fs->_FileName[0];
			size_t iFile = 0;
			while (*Cur && iFile < MAX_PATH)
			{
				bool cabinetFound = false;

				if (*Cur == '\\' || *Cur == '/')
				{
					WCHAR* Ext;
					// See if the file we've collected already is a cabinet.
					HRESULT hr = PathCchFindExtension(FilePath, MAX_PATH, &Ext);
					if (SUCCEEDED(hr))
					{
						if (_wcsicmp(Ext, CABINET_EXT) == 0)
						{
							if (ArchivePath[0] == 0)
							{
								wcscpy_s(ArchivePath, FilePath);
								iFile = 0;
								cabinetFound = true;
							}
						}
					}
				}
				if (!cabinetFound)
				{
					FilePath[iFile] = *Cur;
					++iFile;
				}
				++Cur;
			}
			FilePath[iFile] = 0;

			// They could be trying to load a loose cab.
			if (iFile == 0 && ArchivePath[0] != 0)
			{
				wcscpy_s(FilePath, ArchivePath);
				ArchivePath[0] = 0;
			}

			if (Fs->_Operation == FileOperation::ReadFile)
			{
				if (ArchivePath[0] != '\0')
				{
					ReadCabinet(Fs, ArchivePath, FilePath);
				}
				else
				{
					ReadLoose(Fs, FilePath);
				}
			}
			else if (Fs->_Operation == FileOperation::WriteFile)
			{
				Fs->set_offset(0);
				if (ArchivePath[0] != '\0')
				{
					WriteCabinet(Fs, ArchivePath, FilePath);
				}
				else
				{
					WriteLoose(Fs, FilePath);
				}
			}
			else
			{
				// Invalid operation.
				assert(false);
			}

			if (SUCCEEDED(Fs->_Hr))
			{
				if (Fs->_Operation == FileOperation::ReadFile)
				{
					DBGL(TAG_FILESTREAMER L"Read %d bytes of '%s'", Fs->size(), Fs->_FileName);
					Fs->set_offset(0);
				}
				else if (Fs->_Operation == FileOperation::WriteFile)
				{
					DBGL(TAG_FILESTREAMER L"Wrote %d bytes of '%s'", Fs->size(), Fs->_FileName);
				}
			}
			else
			{
				if (Fs->_Operation == FileOperation::ReadFile)
				{
					DBGL(TAG_FILESTREAMER L"Read failed: %x", Fs->_Hr);
				}
				else if (Fs->_Operation == FileOperation::WriteFile)
				{
					DBGL(TAG_FILESTREAMER L"Write failed: %x", Fs->_Hr);
				}
				Fs->clear();
			}
		}

		void file_streamer::ReadLoose(file_buffer* State, LPCWSTR FilePath)
		{
			HANDLE hFile = CreateFile(FilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
			if (hFile == INVALID_HANDLE_VALUE)
			{
				State->_Hr = HRESULT_FROM_WIN32(GetLastError());
				return;
			}

			char* Buffer = new char[BUFSZ];
			DWORD bytesRead;

			while (::ReadFile(hFile, Buffer, BUFSZ, &bytesRead, NULL))
			{
				State->write(Buffer, bytesRead);
				if (bytesRead < BUFSZ)
				{
					break;
				}
			}

			if (GetLastError() != ERROR_SUCCESS && GetLastError() != ERROR_IO_PENDING)
			{
				State->clear();
				State->_Hr = HRESULT_FROM_WIN32(GetLastError());
			}

			delete[] Buffer;
			CloseHandle(hFile);
		}

		void file_streamer::WriteLoose(file_buffer* State, LPCWSTR FilePath)
		{
			HANDLE hFile = CreateFile(FilePath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			if (hFile == INVALID_HANDLE_VALUE)
			{
				State->_Hr = HRESULT_FROM_WIN32(GetLastError());
				return;
			}

			char* Buffer = new char[BUFSZ];

			for (;;)
			{
				DWORD bytesToWrite = (DWORD)State->read(Buffer, BUFSZ);
				if (bytesToWrite == 0)
				{
					break;
				}

				DWORD bytesWritten;
				if (!::WriteFile(hFile, Buffer, bytesToWrite, &bytesWritten, NULL))
				{
					State->_Hr = HRESULT_FROM_WIN32(GetLastError());
					break;
				}
			}

			delete[] Buffer;
			CloseHandle(hFile);
		}

		/*
		Actual cabinet methods.
		*/

		void file_streamer::ReadCabinet(file_buffer* State, LPCWSTR CabPath, LPCWSTR FileName)
		{
			try
			{
				m::compression::_7za sz;
				const WCHAR* TrueStart = FileName;
				while (*TrueStart && (*TrueStart == '\\' || *TrueStart == '//'))
				{
					++TrueStart;
				}

				sz.extract_file(CabPath, FileName, State);
				if (State->size() == 0)
				{
					State->_Hr = HRESULT_FROM_WIN32(GetLastError());
				}
			}
			catch (bad_result_exception& bre)
			{
				State->_Hr = bre.ErrorCode;
			}
		}

		void file_streamer::WriteCabinet(file_buffer* State, LPCWSTR CabPath, LPCWSTR FileName)
		{
			throw bad_result_exception(MONORET(FACILITY_FILE, ERROR_NOT_SUPPORTED), "Writing to cabinets is not supported at this time.");
		}
	}
}